PART III: German Credit Score Classification Model BIAS & FAIRNESS

By: Krishna J

Importing necessary libraries

In [1]:
import pandas as pd
import numpy as np
import seaborn               as sns
import matplotlib.pyplot     as plt
from sklearn.model_selection import train_test_split
#from sklearn.ensemble        import RandomForestClassifier
#from sklearn.linear_model    import LogisticRegression
from sklearn.preprocessing   import MinMaxScaler, StandardScaler
from sklearn.base            import TransformerMixin
from sklearn.pipeline        import Pipeline, FeatureUnion
from typing                  import List, Union, Dict
# Warnings will be used to silence various model warnings for tidier output
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline 
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
np.random.seed(0)

Importing source dataset

In [2]:
German_df = pd.read_csv('C:/Users/krish/Downloads/German-reduced.csv')

print(German_df.shape)
print (German_df.columns)
(1000, 24)
Index(['Gender', 'Age', 'Marital_Status', 'NumMonths', 'Savings_<500',
       'Savings_none', 'Dependents', 'Property_rent',
       'Job_management/self-emp/officer/highly qualif emp',
       'Debtors_guarantor', 'Purpose_CarNew', 'Purpose_furniture/equip',
       'CreditHistory_none/paid', 'Purpose_CarUsed', 'CreditAmount',
       'Collateral_real estate', 'Debtors_none',
       'Job_unemp/unskilled-non resident', 'Purpose_others',
       'CreditHistory_other', 'PayBackPercent', 'Collateral_unknown/none',
       'Purpose_education', 'CreditStatus'],
      dtype='object')
In [3]:
German_df.head()
Out[3]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education CreditStatus
0 1 1 1 6 0 1 1 0 0 0 ... 0.050567 1 1 0 0 1 4 0 0 1
1 0 0 0 48 1 0 1 0 0 0 ... 0.313690 1 1 0 0 0 2 0 0 0
2 1 1 1 12 1 0 2 0 0 0 ... 0.101574 1 1 0 0 1 2 0 1 1
3 1 1 1 42 1 0 2 0 0 1 ... 0.419941 0 0 0 0 0 2 0 0 1
4 1 1 1 24 1 0 2 0 0 0 ... 0.254209 0 1 0 0 0 3 1 0 0

5 rows × 24 columns

In [4]:
#feature_list = ['Gender','Age','Marital_Status','NumMonths','Savings_<500','Savings_none','Dependents','Property_rent','Job_management/self-emp/officer/highly qualif emp','Debtors_guarantor','Purpose_CarNew',                           'Purpose_furniture/equip','CreditHistory_none/paid','Purpose_CarUsed','CreditAmount','CreditStatus']
feature_list=['Gender','Age','Marital_Status','NumMonths','Savings_<500','Savings_none','Dependents','Property_rent',
                           'Job_management/self-emp/officer/highly qualif emp','Debtors_guarantor','Purpose_CarNew',
                           'Purpose_furniture/equip','CreditHistory_none/paid','Purpose_CarUsed','CreditAmount',
                           'Collateral_real estate','Debtors_none','Job_unemp/unskilled-non resident','Purpose_others',             
                            'CreditHistory_other','PayBackPercent','Collateral_unknown/none','Purpose_education', 'CreditStatus']
In [5]:
X = German_df.iloc[:, :-1]
y = German_df['CreditStatus']
X.head()
y.head()
Out[5]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... Purpose_CarUsed CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education
0 1 1 1 6 0 1 1 0 0 0 ... 0 0.050567 1 1 0 0 1 4 0 0
1 0 0 0 48 1 0 1 0 0 0 ... 0 0.313690 1 1 0 0 0 2 0 0
2 1 1 1 12 1 0 2 0 0 0 ... 0 0.101574 1 1 0 0 1 2 0 1
3 1 1 1 42 1 0 2 0 0 1 ... 0 0.419941 0 0 0 0 0 2 0 0
4 1 1 1 24 1 0 2 0 0 0 ... 0 0.254209 0 1 0 0 0 3 1 0

5 rows × 23 columns

Out[5]:
0    1
1    0
2    1
3    1
4    0
Name: CreditStatus, dtype: int64
In [6]:
from imblearn.over_sampling import ADASYN
from collections import Counter

ada = ADASYN(random_state=40)
print('Original dataset shape {}'.format(Counter(y)))
X_res, y_res = ada.fit_resample(X,y)
print('Resampled dataset shape {}'.format(Counter(y_res)))
Original dataset shape Counter({1: 700, 0: 300})
Resampled dataset shape Counter({0: 705, 1: 700})
In [7]:
German_df=X = pd.DataFrame(np.column_stack((X_res, y_res)))
In [8]:
German_df.head()
Out[8]:
0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23
0 1.0 1.0 1.0 6.0 0.0 1.0 1.0 0.0 0.0 0.0 ... 0.050567 1.0 1.0 0.0 0.0 1.0 4.0 0.0 0.0 1.0
1 0.0 0.0 0.0 48.0 1.0 0.0 1.0 0.0 0.0 0.0 ... 0.313690 1.0 1.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0
2 1.0 1.0 1.0 12.0 1.0 0.0 2.0 0.0 0.0 0.0 ... 0.101574 1.0 1.0 0.0 0.0 1.0 2.0 0.0 1.0 1.0
3 1.0 1.0 1.0 42.0 1.0 0.0 2.0 0.0 0.0 1.0 ... 0.419941 0.0 0.0 0.0 0.0 0.0 2.0 0.0 0.0 1.0
4 1.0 1.0 1.0 24.0 1.0 0.0 2.0 0.0 0.0 0.0 ... 0.254209 0.0 1.0 0.0 0.0 0.0 3.0 1.0 0.0 0.0

5 rows × 24 columns

In [9]:
German_df.columns=feature_list
German_df.head()
Out[9]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education CreditStatus
0 1.0 1.0 1.0 6.0 0.0 1.0 1.0 0.0 0.0 0.0 ... 0.050567 1.0 1.0 0.0 0.0 1.0 4.0 0.0 0.0 1.0
1 0.0 0.0 0.0 48.0 1.0 0.0 1.0 0.0 0.0 0.0 ... 0.313690 1.0 1.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0
2 1.0 1.0 1.0 12.0 1.0 0.0 2.0 0.0 0.0 0.0 ... 0.101574 1.0 1.0 0.0 0.0 1.0 2.0 0.0 1.0 1.0
3 1.0 1.0 1.0 42.0 1.0 0.0 2.0 0.0 0.0 1.0 ... 0.419941 0.0 0.0 0.0 0.0 0.0 2.0 0.0 0.0 1.0
4 1.0 1.0 1.0 24.0 1.0 0.0 2.0 0.0 0.0 0.0 ... 0.254209 0.0 1.0 0.0 0.0 0.0 3.0 1.0 0.0 0.0

5 rows × 24 columns

Metrics to calculate model fairness necessary libraries

In [10]:
from aif360.datasets import GermanDataset
from aif360.metrics import BinaryLabelDatasetMetric

def fair_metrics(fname, dataset, pred, pred_is_dataset=False):
    filename = fname
    if pred_is_dataset:
        dataset_pred = pred
    else:
        dataset_pred = dataset.copy()
        dataset_pred.labels = pred

    cols = ['Accuracy', 'F1', 'DI','SPD', 'EOD', 'AOD', 'ERD', 'CNT', 'TI']
    obj_fairness = [[1,1,1,0,0,0,0,1,0]]

    fair_metrics = pd.DataFrame(data=obj_fairness, index=['objective'], columns=cols)

    for attr in dataset_pred.protected_attribute_names:
        idx = dataset_pred.protected_attribute_names.index(attr)
        privileged_groups =  [{attr:dataset_pred.privileged_protected_attributes[idx][0]}]
        unprivileged_groups = [{attr:dataset_pred.unprivileged_protected_attributes[idx][0]}]

        classified_metric = ClassificationMetric(dataset,
                                                     dataset_pred,
                                                     unprivileged_groups=unprivileged_groups,
                                                     privileged_groups=privileged_groups)

        metric_pred = BinaryLabelDatasetMetric(dataset_pred,
                                                     unprivileged_groups=unprivileged_groups,
                                                     privileged_groups=privileged_groups)

        distortion_metric = SampleDistortionMetric(dataset,
                                                     dataset_pred,
                                                     unprivileged_groups=unprivileged_groups,
                                                     privileged_groups=privileged_groups)

        acc = classified_metric.accuracy()
        f1_sc = 2 * (classified_metric.precision() * classified_metric.recall()) / (classified_metric.precision() + classified_metric.recall())

        mt = [acc, f1_sc,
                        classified_metric.disparate_impact(),
                        classified_metric.mean_difference(),
                        classified_metric.equal_opportunity_difference(),
                        classified_metric.average_odds_difference(),
                        classified_metric.error_rate_difference(),
                        metric_pred.consistency(),
                        classified_metric.theil_index()
                    ]
        w_row = []
        print('Computing fairness of the model.')
        for i in mt:
            #print("%.8f"%i)
            w_row.append("%.8f"%i)
        with open(filename, 'a') as csvfile:
            csvwriter = csv.writer(csvfile)
            csvwriter.writerow(w_row)
        row = pd.DataFrame([mt],
                           columns  = cols,
                           index = [attr]
                          )
        fair_metrics = fair_metrics.append(row)
    fair_metrics = fair_metrics.replace([-np.inf, np.inf], 2)
    return fair_metrics

def get_fair_metrics_and_plot(fname, data, model, plot=False, model_aif=False):
    pred = model.predict(data).labels if model_aif else model.predict(data.features)
    fair = fair_metrics(fname, data, pred)
    if plot:
        pass

    return fair

def get_model_performance(X_test, y_true, y_pred, probs):
    accuracy = accuracy_score(y_true, y_pred)
    matrix = confusion_matrix(y_true, y_pred)
    f1 = f1_score(y_true, y_pred)
    return accuracy, matrix, f1

def plot_model_performance(model, X_test, y_true):
    y_pred = model.predict(X_test)
    probs = model.predict_proba(X_test)
    accuracy, matrix, f1 = get_model_performance(X_test, y_true, y_pred, probs)

Local file to load metric values

In [11]:
filename= 'C:/Users/krish/Downloads/filename_mainpjt_results_apr_14_corrected_tgt_no_adasyn_copy.csv'

Converting data to aif compatible format

Since we are dealing with binary label dataset we are using aif360 class BiaryLabelDataset here with target label as CreditStatus and protected attributes as age,gender,marital status. Refer part 11 for more details on protected attributes and privileged classes.

In [12]:
# Fairness metrics
from aif360.metrics import BinaryLabelDatasetMetric
from aif360.explainers import MetricTextExplainer
from aif360.metrics import ClassificationMetric
# Get DF into IBM format
from aif360 import datasets
#converting to aif dataset
aif_dataset = datasets.BinaryLabelDataset(favorable_label = 1, unfavorable_label = 0, df=German_df,
                                                      label_names=["CreditStatus"],
                                                     protected_attribute_names=["Age","Gender","Marital_Status"],
                                              privileged_protected_attributes = [1,1,1])
In [13]:
#dataset_orig = GermanDataset(protected_attribute_names=['sex'],
#                            privileged_classes=[[1]],
#                            features_to_keep=['age', 'sex', 'employment', 'housing', 'savings', 'credit_amount', 'month', 'purpose'],
#                            custom_preprocessing=custom_preprocessing)

Splitting data to train and test sets

In [14]:
#privileged_groups = [{'Age':1},{'Gender': 1},{'Marital_Status':1}]
#unprivileged_groups = [{'Age':0},{'Gender': 0},{'Marital_Status':0}]
In [15]:
privileged_groups = [{'Gender': 1}]
unprivileged_groups = [{'Gender': 0}]
In [16]:
data_orig_train, data_orig_test = aif_dataset.split([0.7], shuffle=True)

X_train = data_orig_train.features
y_train = data_orig_train.labels.ravel()

X_test = data_orig_test.features
y_test = data_orig_test.labels.ravel()
In [17]:
X_train.shape
X_test.shape
Out[17]:
(983, 23)
Out[17]:
(422, 23)
In [18]:
data_orig_test.labels[:10].ravel()
Out[18]:
array([1., 1., 1., 0., 0., 1., 0., 1., 1., 1.])
In [19]:
data_orig_train.labels[:10].ravel()
Out[19]:
array([1., 1., 1., 1., 0., 1., 1., 0., 0., 1.])

Building ML model

Considering ensemble models for our study.

1. RANDOM FOREST CLASSIFIER MODEL

In [20]:
#Seting the Hyper Parameters
param_grid = {"max_depth": [3,5,7, 10,None],
              "n_estimators":[3,5,10,25,50,150],
              "max_features": [4,7,15,20]}
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
#Creating the classifier
rf_model = RandomForestClassifier(random_state=40)
grid_search = GridSearchCV(rf_model, param_grid=param_grid, cv=5, scoring='recall', verbose=0)
model = grid_search
In [21]:
mdl_rf = model.fit(data_orig_train.features, data_orig_train.labels.ravel())
In [22]:
from sklearn.metrics import confusion_matrix
conf_mat_rf = confusion_matrix(data_orig_test.labels.ravel(), mdl_rf.predict(data_orig_test.features))
conf_mat_rf
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), mdl_rf.predict(data_orig_test.features)))
Out[22]:
array([[167,  39],
       [ 59, 157]], dtype=int64)
0.7677725118483413
In [23]:
unique, counts = np.unique(data_orig_test.labels.ravel(), return_counts=True)
dict(zip(unique, counts))
Out[23]:
{0.0: 206, 1.0: 216}

1.a. Feature importance of model

In [24]:
importances = model.best_estimator_.feature_importances_
indices = np.argsort(importances)
features = data_orig_train.feature_names
#https://stackoverflow.com/questions/48377296/get-feature-importance-from-gridsearchcv
In [25]:
importances
Out[25]:
array([0.02583933, 0.03939433, 0.03309587, 0.15685122, 0.01904122,
       0.04450764, 0.01890931, 0.02058704, 0.0221092 , 0.01100163,
       0.02956672, 0.02434076, 0.02393366, 0.02904432, 0.27269205,
       0.04567911, 0.02029773, 0.00519504, 0.00363725, 0.06382845,
       0.06219648, 0.01792259, 0.01032902])
In [26]:
importances[indices]
Out[26]:
array([0.00363725, 0.00519504, 0.01032902, 0.01100163, 0.01792259,
       0.01890931, 0.01904122, 0.02029773, 0.02058704, 0.0221092 ,
       0.02393366, 0.02434076, 0.02583933, 0.02904432, 0.02956672,
       0.03309587, 0.03939433, 0.04450764, 0.04567911, 0.06219648,
       0.06382845, 0.15685122, 0.27269205])
In [27]:
features
Out[27]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [28]:
plt.figure(figsize=(20,30))
plt.title('Feature Importances')
plt.barh(range(len(indices)), importances[indices], color='b', align='center')
plt.yticks(range(len(indices)), [features[i] for i in indices])
plt.xlabel('Relative Importance')
plt.show()
Out[28]:
<Figure size 1440x2160 with 0 Axes>
Out[28]:
Text(0.5, 1.0, 'Feature Importances')
Out[28]:
<BarContainer object of 23 artists>
Out[28]:
([<matplotlib.axis.YTick at 0x1a5f7e2cc48>,
  <matplotlib.axis.YTick at 0x1a5f96b7888>,
  <matplotlib.axis.YTick at 0x1a5f96c3148>,
  <matplotlib.axis.YTick at 0x1a5fb77bf08>,
  <matplotlib.axis.YTick at 0x1a5fb77ec48>,
  <matplotlib.axis.YTick at 0x1a5fb781488>,
  <matplotlib.axis.YTick at 0x1a5fb781cc8>,
  <matplotlib.axis.YTick at 0x1a5fb7864c8>,
  <matplotlib.axis.YTick at 0x1a5fb786c48>,
  <matplotlib.axis.YTick at 0x1a5fb781948>,
  <matplotlib.axis.YTick at 0x1a5fb781248>,
  <matplotlib.axis.YTick at 0x1a5fb78bb88>,
  <matplotlib.axis.YTick at 0x1a5fb788448>,
  <matplotlib.axis.YTick at 0x1a5fb788c88>,
  <matplotlib.axis.YTick at 0x1a5fb7926c8>,
  <matplotlib.axis.YTick at 0x1a5fb795108>,
  <matplotlib.axis.YTick at 0x1a5fb795b08>,
  <matplotlib.axis.YTick at 0x1a5fb79c548>,
  <matplotlib.axis.YTick at 0x1a5fb79d148>,
  <matplotlib.axis.YTick at 0x1a5fb795088>,
  <matplotlib.axis.YTick at 0x1a5fb788cc8>,
  <matplotlib.axis.YTick at 0x1a5fb79de88>,
  <matplotlib.axis.YTick at 0x1a5fb7a2748>],
 <a list of 23 Text yticklabel objects>)
Out[28]:
Text(0.5, 0, 'Relative Importance')

1.b. Model Explainability/interpretability

1.b.1 Using SHAP (SHapley Additive exPlanations)

In [30]:
import shap

Test data interpretation

In [31]:
rf_explainer = shap.KernelExplainer(mdl_rf.predict, data_orig_test.features)
rf_shap_values = rf_explainer.shap_values(data_orig_test.features,nsamples=50)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
Using 422 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [32]:
rf_shap_values
Out[32]:
array([[ 0.06916089,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.14210396,  0.        ,  0.        , ...,  0.        ,
         0.0672571 ,  0.        ],
       ...,
       [ 0.05412601,  0.07096459,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.05055835,  0.        , ...,  0.0299452 ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.00058216,
        -0.05417849,  0.        ]])
In [33]:
rf_explainer.expected_value
Out[33]:
0.46445497630331756
In [34]:
y_test_predict=mdl_rf.predict(data_orig_test.features)
y_test_predict[:12]
data_orig_test.labels[:12].ravel()
data_orig_test.features[:2,:]
Out[34]:
array([1., 0., 1., 0., 0., 1., 0., 0., 1., 0., 1., 1.])
Out[34]:
array([1., 1., 1., 0., 0., 1., 0., 1., 1., 1., 0., 1.])
Out[34]:
array([[ 0.        ,  0.        ,  0.        , 18.        ,  1.        ,
         0.        ,  1.        ,  1.        ,  0.        ,  0.        ,
         0.        ,  1.        ,  0.        ,  0.        ,  0.0439639 ,
         0.        ,  1.        ,  0.        ,  0.        ,  1.        ,
         4.        ,  0.        ,  0.        ],
       [ 1.        ,  1.        ,  1.        , 12.        ,  1.        ,
         0.        ,  1.        ,  0.        ,  0.        ,  0.        ,
         1.        ,  0.        ,  1.        ,  0.        ,  0.32067789,
         0.        ,  1.        ,  0.        ,  0.        ,  0.        ,
         2.        ,  0.        ,  0.        ]])
In [349]:
y_test_predict.mean()
Out[349]:
0.46445497630331756

The explainer expected value is the average model predicted value on input data. Shapely helps to understand how individual features impact the output of each individual instance. The shapely values are model predicted values which may not coincide with actual y test values due to prediction error.

link=”logit” argument converts the logit values to probability

In [345]:
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[0],data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
#https://github.com/slundberg/shap/issues/977
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[0],data_orig_test.features[0],data_orig_test.feature_names)
Out[345]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
Out[345]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

Features in blue pushes the base value towards lowest values and features in red moves base levels towards higher values.

Shapley values calculate the importance of a feature by comparing what a model predicts with and without the feature. However, since the order in which a model sees features can affect its predictions, this is done in every possible order, so that the features are fairly compared.

The SHAP plot shows features that contribute to pushing the output from the base value (average model output) to the actual predicted value.

In [346]:
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[1], data_orig_test.features[1],data_orig_test.feature_names,link='logit')
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[1], data_orig_test.features[1],data_orig_test.feature_names)
Out[346]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
Out[346]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [37]:
data_orig_test.feature_names
Out[37]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [38]:
shap.force_plot(rf_explainer.expected_value,
                rf_shap_values, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[38]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [39]:
p = shap.summary_plot(rf_shap_values, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

Variables with higher impact are displayed at the credit history, credit amount,num of months.

In [40]:
shap.plots._waterfall.waterfall_legacy(rf_explainer.expected_value, rf_shap_values[0],feature_names=data_orig_test.feature_names)

For first instace of input,out of all the displayed variables, CreditHistory with value other is playing major role is pushing the target variable outcome towards predicting 1.

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

Shapley values calculate the importance of a feature by comparing what a model predicts with and without the feature. However, since the order in which a model sees features can affect its predictions, this is done in every possible order, so that the features are fairly compared. https://medium.com/@gabrieltseng/interpreting-complex-models-with-shap-values-1c187db6ec83

In [41]:
shap.plots._waterfall.waterfall_legacy(rf_explainer.expected_value, rf_shap_values[1],feature_names=data_orig_test.feature_names)

For second instace of input,out of all the displayed variables, credit amount is playing major role is pushing the target variable outcome towards predicting 0.

1.b.2 Using ELI5

In [42]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [43]:
perm_rf = PermutationImportance(mdl_rf).fit(data_orig_test.features, data_orig_test.labels.ravel())

Feature Importance

In [44]:
perm_imp_1=eli5.show_weights(perm_rf,feature_names = data_orig_test.feature_names)
perm_imp_1
plt.show()
Out[44]:
Weight Feature
0.0657 ± 0.0368 CreditHistory_other
0.0389 ± 0.0246 NumMonths
0.0370 ± 0.0194 Purpose_CarUsed
0.0296 ± 0.0239 Collateral_real estate
0.0269 ± 0.0148 Savings_none
0.0241 ± 0.0148 CreditHistory_none/paid
0.0213 ± 0.0278 CreditAmount
0.0065 ± 0.0094 Property_rent
0.0037 ± 0.0069 Debtors_guarantor
0.0019 ± 0.0094 Dependents
0.0000 ± 0.0083 Savings_<500
0 ± 0.0000 Job_unemp/unskilled-non resident
0.0000 ± 0.0059 Gender
-0.0009 ± 0.0069 Debtors_none
-0.0019 ± 0.0045 Purpose_others
-0.0028 ± 0.0045 Collateral_unknown/none
-0.0028 ± 0.0045 Purpose_education
-0.0074 ± 0.0139 Purpose_furniture/equip
-0.0083 ± 0.0358 Marital_Status
-0.0102 ± 0.0108 Job_management/self-emp/officer/highly qualif emp
… 3 more …

eli5 provides a way to compute feature importances for any black-box estimator by measuring how score decreases when a feature is not available; the method is also known as “permutation importance” or “Mean Decrease Accuracy (MDA)”.

The first number in each row shows how much model performance decreased with a random shuffling (in this case, using "accuracy" as the performance metric).

Like most things in data science, there is some randomness to the exact performance change from a shuffling a column. We measure the amount of randomness in our permutation importance calculation by repeating the process with multiple shuffles. The number after the ± measures how performance varied from one-reshuffling to the next.

You'll occasionally see negative values for permutation importances. In those cases, the predictions on the shuffled (or noisy) data happened to be more accurate than the real data. This happens when the feature didn't matter (should have had an importance close to 0), but random chance caused the predictions on shuffled data to be more accurate. This is more common with small datasets, like the one in this example, because there is more room for luck/chance.

https://www.kaggle.com/dansbecker/permutation-importance

1.c. Measuring fairness

Of Baseline model

In [45]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_rf, X_test, y_test)
In [46]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, mdl_rf)
fair
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[46]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.767773 0.762136 0.430435 -0.302042 -0.287766 -0.226245 0.008827 0.704265 0.183044
Gender 0.767773 0.762136 0.682051 -0.167341 -0.124266 -0.114846 -0.002313 0.704265 0.183044
Marital_Status 0.767773 0.762136 0.724699 -0.149178 -0.060360 -0.079329 -0.027127 0.704265 0.183044
In [47]:
type(data_orig_train)
Out[47]:
aif360.datasets.binary_label_dataset.BinaryLabelDataset

PRE PROCESSING

In [48]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train = RW.fit_transform(data_orig_train)

data_transf_test = RW.transform(data_orig_test)
fair_rw = get_fair_metrics_and_plot(filename, data_transf_test, mdl_rf, plot=False)
WARNING:root:No module named 'numba.decorators': LFR will be unavailable. To install, run:
pip install 'aif360[LFR]'
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [49]:
fair_rw
Out[49]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.759804 0.755868 0.474829 -0.277017 -0.285735 -0.223473 0.030011 0.704265 0.183044
Gender 0.759804 0.755868 0.810395 -0.095171 -0.124266 -0.114846 0.018944 0.704265 0.183044
Marital_Status 0.759804 0.755868 0.806877 -0.100225 -0.070405 -0.082321 -0.011739 0.704265 0.183044
In [50]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train = DIR.fit_transform(data_orig_train)

# Train and save the model
#rf_transf = model.fit(data_transf_train.features,data_transf_train.labels.ravel())
In [51]:
fair_dir = get_fair_metrics_and_plot(filename, data_orig_test, mdl_rf, plot=False)
fair_dir
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[51]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.767773 0.762136 0.430435 -0.302042 -0.287766 -0.226245 0.008827 0.704265 0.183044
Gender 0.767773 0.762136 0.682051 -0.167341 -0.124266 -0.114846 -0.002313 0.704265 0.183044
Marital_Status 0.767773 0.762136 0.724699 -0.149178 -0.060360 -0.079329 -0.027127 0.704265 0.183044

INPROCESSING

In [52]:
#!pip install --user --upgrade tensorflow==1.15.0
#2.2.0
#!pip uninstall tensorflow
In [53]:
#!pip install "tensorflow==1.15"
#!pip install --upgrade tensorflow-hub
In [54]:
#%tensorflow_version 1.15
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [55]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [56]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [57]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope1',reuse=tf.AUTO_REUSE) as scope:
        debiased_model = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model.fit(data_orig_train)
        fair_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:141: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:141: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:84: The name tf.get_variable is deprecated. Please use tf.compat.v1.get_variable instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:84: The name tf.get_variable is deprecated. Please use tf.compat.v1.get_variable instead.

WARNING:tensorflow:
The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
  * https://github.com/tensorflow/addons
  * https://github.com/tensorflow/io (for I/O related ops)
If you depend on functionality not listed there, please file an issue.

WARNING:tensorflow:
The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
  * https://github.com/tensorflow/addons
  * https://github.com/tensorflow/io (for I/O related ops)
If you depend on functionality not listed there, please file an issue.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:89: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:89: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\tensorflow_core\python\ops\nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\tensorflow_core\python\ops\nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:159: The name tf.train.exponential_decay is deprecated. Please use tf.compat.v1.train.exponential_decay instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:159: The name tf.train.exponential_decay is deprecated. Please use tf.compat.v1.train.exponential_decay instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:161: The name tf.train.AdamOptimizer is deprecated. Please use tf.compat.v1.train.AdamOptimizer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:161: The name tf.train.AdamOptimizer is deprecated. Please use tf.compat.v1.train.AdamOptimizer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:165: The name tf.trainable_variables is deprecated. Please use tf.compat.v1.trainable_variables instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:165: The name tf.trainable_variables is deprecated. Please use tf.compat.v1.trainable_variables instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:187: The name tf.global_variables_initializer is deprecated. Please use tf.compat.v1.global_variables_initializer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:187: The name tf.global_variables_initializer is deprecated. Please use tf.compat.v1.global_variables_initializer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:188: The name tf.local_variables_initializer is deprecated. Please use tf.compat.v1.local_variables_initializer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:188: The name tf.local_variables_initializer is deprecated. Please use tf.compat.v1.local_variables_initializer instead.

epoch 0; iter: 0; batch classifier loss: 0.929297; batch adversarial loss: 0.675813
epoch 1; iter: 0; batch classifier loss: 0.849422; batch adversarial loss: 0.649724
epoch 2; iter: 0; batch classifier loss: 0.718864; batch adversarial loss: 0.652960
epoch 3; iter: 0; batch classifier loss: 0.832506; batch adversarial loss: 0.676355
epoch 4; iter: 0; batch classifier loss: 0.678822; batch adversarial loss: 0.672013
epoch 5; iter: 0; batch classifier loss: 0.713734; batch adversarial loss: 0.670827
epoch 6; iter: 0; batch classifier loss: 0.676389; batch adversarial loss: 0.619652
epoch 7; iter: 0; batch classifier loss: 0.697515; batch adversarial loss: 0.657454
epoch 8; iter: 0; batch classifier loss: 0.693671; batch adversarial loss: 0.716555
epoch 9; iter: 0; batch classifier loss: 0.697642; batch adversarial loss: 0.642780
Out[57]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1a582b7d848>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [58]:
fair_ad
Out[58]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.71327 0.708434 0.400777 -0.325033 -0.300710 -0.264214 -0.019170 [0.8350710900473931] 0.219112
Gender 0.71327 0.708434 0.771664 -0.117602 -0.119178 -0.076643 0.033256 [0.8350710900473931] 0.219112
Marital_Status 0.71327 0.708434 0.899408 -0.050048 -0.050329 0.009449 0.049419 [0.8350710900473931] 0.219112
In [59]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model = PrejudiceRemover()

# Train and save the model
debiased_model.fit(data_orig_train)

fair_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
fair_pr
Out[59]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1a582c45e08>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[59]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.744076 0.741627 0.267114 -0.417523 -0.481294 -0.361982 0.048024 [0.7355450236966818] 0.193434
Gender 0.744076 0.741627 0.720477 -0.149219 -0.173973 -0.103168 0.061789 [0.7355450236966818] 0.193434
Marital_Status 0.744076 0.741627 0.744863 -0.140765 -0.155483 -0.073803 0.075489 [0.7355450236966818] 0.193434

POST PROCESSING

In [60]:
y_pred = debiased_model.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [61]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_rf.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_rf.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)
In [62]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP = EOPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = EOPP.predict(data_orig_test_pred)
fair_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [63]:
fair_eo
Out[63]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.739336 0.727723 0.550147 -0.222200 -0.198421 -0.151901 0.000264 [0.6890995260663499] 0.212913
Gender 0.739336 0.727723 0.880066 -0.055909 -0.013503 -0.007914 -0.006748 [0.6890995260663499] 0.212913
Marital_Status 0.739336 0.727723 0.851272 -0.071800 0.005707 -0.007987 -0.029287 [0.6890995260663499] 0.212913
In [64]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=42)

CPP = CPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = CPP.predict(data_orig_test_pred)
fair_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [65]:
fair_ceo
Out[65]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.668246 0.717742 0.580462 -0.306390 -0.233307 -0.252766 -0.007246 [0.7246445497630324] 0.149747
Gender 0.668246 0.717742 0.426282 -0.483131 -0.268102 -0.449264 -0.160208 [0.7246445497630324] 0.149747
Marital_Status 0.668246 0.717742 0.599785 -0.335155 -0.144933 -0.301927 -0.101131 [0.7246445497630324] 0.149747
In [66]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC = ROC.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = ROC.predict(data_orig_test_pred)
fair_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
SUCCESS: completed 1 model.
In [67]:
fair_roc
Out[67]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.779621 0.779097 0.567428 -0.232016 -0.190055 -0.147718 0.010079 [0.6895734597156391] 0.164704
Gender 0.779621 0.779097 0.963202 -0.018122 0.060274 0.038955 -0.024195 [0.6895734597156391] 0.164704
Marital_Status 0.779621 0.779097 0.917941 -0.041636 0.067105 0.032726 -0.040466 [0.6895734597156391] 0.164704

2. XGBoost Classifier

In [68]:
from xgboost import XGBClassifier
estimator = XGBClassifier(seed=40)

parameters = {
    'max_depth': range (2, 10, 2),
    'n_estimators': range(60, 240, 40),
    'learning_rate': [0.1, 0.01, 0.05]
}
grid_search = GridSearchCV(
    estimator=estimator,
    param_grid=parameters,
    scoring = 'recall',
    
    cv = 5,
    verbose=0
)

model_xg=grid_search
In [69]:
mdl_xgb = model_xg.fit(data_orig_train.features, data_orig_train.labels.ravel())

2.a. Feature importance of model

In [70]:
importances_xg = model_xg.best_estimator_.feature_importances_
indices_xg = np.argsort(importances_xg)
features = data_orig_train.feature_names
#https://stackoverflow.com/questions/48377296/get-feature-importance-from-gridsearchcv
In [71]:
importances_xg
Out[71]:
array([0.02317642, 0.05685762, 0.03352444, 0.03343777, 0.02155409,
       0.10331297, 0.03489636, 0.03127367, 0.04115128, 0.04509035,
       0.03441571, 0.03500502, 0.0414577 , 0.07110896, 0.02757771,
       0.08931217, 0.03350579, 0.02026273, 0.00716088, 0.14619814,
       0.02927825, 0.02007822, 0.02036368], dtype=float32)
In [72]:
importances_xg[indices_xg]
Out[72]:
array([0.00716088, 0.02007822, 0.02026273, 0.02036368, 0.02155409,
       0.02317642, 0.02757771, 0.02927825, 0.03127367, 0.03343777,
       0.03350579, 0.03352444, 0.03441571, 0.03489636, 0.03500502,
       0.04115128, 0.0414577 , 0.04509035, 0.05685762, 0.07110896,
       0.08931217, 0.10331297, 0.14619814], dtype=float32)
In [73]:
features
Out[73]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [74]:
plt.figure(figsize=(20,30))
plt.title('Feature Importances')
plt.barh(range(len(indices_xg)), importances_xg[indices_xg], color='b', align='center')
plt.yticks(range(len(indices_xg)), [features[i] for i in indices_xg])
plt.xlabel('Relative Importance')
plt.show()
Out[74]:
<Figure size 1440x2160 with 0 Axes>
Out[74]:
Text(0.5, 1.0, 'Feature Importances')
Out[74]:
<BarContainer object of 23 artists>
Out[74]:
([<matplotlib.axis.YTick at 0x1a5858afe88>,
  <matplotlib.axis.YTick at 0x1a5858bc788>,
  <matplotlib.axis.YTick at 0x1a58591ad48>,
  <matplotlib.axis.YTick at 0x1a586931308>,
  <matplotlib.axis.YTick at 0x1a5869333c8>,
  <matplotlib.axis.YTick at 0x1a586933bc8>,
  <matplotlib.axis.YTick at 0x1a586937508>,
  <matplotlib.axis.YTick at 0x1a586937b88>,
  <matplotlib.axis.YTick at 0x1a586937188>,
  <matplotlib.axis.YTick at 0x1a586933188>,
  <matplotlib.axis.YTick at 0x1a58693bac8>,
  <matplotlib.axis.YTick at 0x1a586940408>,
  <matplotlib.axis.YTick at 0x1a586940cc8>,
  <matplotlib.axis.YTick at 0x1a586944708>,
  <matplotlib.axis.YTick at 0x1a586947288>,
  <matplotlib.axis.YTick at 0x1a586947bc8>,
  <matplotlib.axis.YTick at 0x1a58694c6c8>,
  <matplotlib.axis.YTick at 0x1a586950308>,
  <matplotlib.axis.YTick at 0x1a58694c8c8>,
  <matplotlib.axis.YTick at 0x1a58694ce48>,
  <matplotlib.axis.YTick at 0x1a5869530c8>,
  <matplotlib.axis.YTick at 0x1a586953c08>,
  <matplotlib.axis.YTick at 0x1a586955488>],
 <a list of 23 Text yticklabel objects>)
Out[74]:
Text(0.5, 0, 'Relative Importance')

2.b. Model Explainability/interpretability

2.b.1 Using SHAP (SHapley Additive exPlanations)

In [75]:
import shap
xg_shap_values_t1 = shap.KernelExplainer(mdl_xgb.predict,data_orig_train.features)
WARNING:shap:Using 983 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

Test data interpretation

In [76]:
xgb_explainer = shap.KernelExplainer(mdl_xgb.predict, data_orig_test.features)
xgb_shap_values = xgb_explainer.shap_values(data_orig_test.features,nsamples=10)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
WARNING:shap:Using 422 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [77]:
xgb_shap_values
Out[77]:
array([[0.        , 0.18957346, 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.30094787, 0.        , ..., 0.        , 0.        ,
        0.        ],
       ...,
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ]])
In [78]:
shap.initjs()
shap.force_plot(xgb_explainer.expected_value,xgb_shap_values[0,:], data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
Out[78]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [79]:
shap.initjs()
shap.force_plot(xgb_explainer.expected_value,xgb_shap_values[1,:], data_orig_test.features[1],data_orig_test.feature_names,link='logit')
Out[79]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [80]:
shap.force_plot(xgb_explainer.expected_value,
                xgb_shap_values, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[80]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [81]:
p = shap.summary_plot(xgb_shap_values, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

The variables with higher impact are the ones in the top age,gender,marital status.

In [82]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer.expected_value, xgb_shap_values[0,:],feature_names=data_orig_test.feature_names)

Here credit history other and age are moving target outcome towards right i.e., 1.

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

In [83]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer.expected_value, xgb_shap_values[1],feature_names=data_orig_test.feature_names)

Here Credit Amount is moving the target result towards zero.

2.b.2 Using ELI5

In [84]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [85]:
perm_xgb = PermutationImportance(mdl_xgb).fit(data_orig_test.features, data_orig_test.labels.ravel())

Feature Importance

In [86]:
perm_imp_2=eli5.show_weights(perm_xgb,feature_names = data_orig_test.feature_names)
perm_imp_2
plt.show()
Out[86]:
Weight Feature
0.0500 ± 0.0189 CreditHistory_other
0.0454 ± 0.0189 NumMonths
0.0370 ± 0.0442 CreditAmount
0.0278 ± 0.0117 CreditHistory_none/paid
0.0250 ± 0.0150 Collateral_real estate
0.0167 ± 0.0111 Debtors_none
0.0157 ± 0.0045 Property_rent
0.0093 ± 0.0101 Gender
0.0083 ± 0.0069 Debtors_guarantor
0.0065 ± 0.0284 Purpose_CarUsed
0.0065 ± 0.0172 PayBackPercent
0.0056 ± 0.0108 Dependents
0.0037 ± 0.0108 Savings_none
0.0019 ± 0.0111 Purpose_furniture/equip
0.0009 ± 0.0258 Age
0.0009 ± 0.0037 Job_unemp/unskilled-non resident
-0.0009 ± 0.0037 Purpose_others
-0.0019 ± 0.0094 Savings_<500
-0.0028 ± 0.0094 Collateral_unknown/none
-0.0028 ± 0.0191 Purpose_CarNew
… 3 more …

2.c. Measuring fairness

Of Baseline model

In [87]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_xgb, X_test, y_test)
In [88]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, mdl_xgb)
fair
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[88]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.760664 0.75895 0.392017 -0.337154 -0.367009 -0.270517 0.041436 0.708531 0.180427
Gender 0.760664 0.75895 0.649573 -0.193657 -0.180235 -0.143703 0.027087 0.708531 0.180427
Marital_Status 0.760664 0.75895 0.681341 -0.183661 -0.095988 -0.116540 -0.022921 0.708531 0.180427

PRE PROCESSING

In [89]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train = RW.fit_transform(data_orig_train)

data_transf_test = RW.transform(data_orig_test)
fair_rw = get_fair_metrics_and_plot(filename, data_transf_test, mdl_xgb, plot=False)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [90]:
fair_rw
Out[90]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.750875 0.750493 0.416489 -0.322117 -0.377363 -0.275150 0.068122 0.708531 0.180427
Gender 0.750875 0.750493 0.761075 -0.126188 -0.180235 -0.143703 0.046445 0.708531 0.180427
Marital_Status 0.750875 0.750493 0.747601 -0.139938 -0.109649 -0.122800 -0.010349 0.708531 0.180427
In [91]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train = DIR.fit_transform(data_orig_train)
In [92]:
fair_dir = get_fair_metrics_and_plot(filename, data_orig_test, mdl_xgb, plot=False)
fair_dir
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[92]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.760664 0.75895 0.392017 -0.337154 -0.367009 -0.270517 0.041436 0.708531 0.180427
Gender 0.760664 0.75895 0.649573 -0.193657 -0.180235 -0.143703 0.027087 0.708531 0.180427
Marital_Status 0.760664 0.75895 0.681341 -0.183661 -0.095988 -0.116540 -0.022921 0.708531 0.180427

INPROCESSING

In [93]:
#!pip install tensorflow
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [94]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [95]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [96]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope2',reuse=tf.AUTO_REUSE) as scope:
        debiased_model = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model.fit(data_orig_train)
        fair_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 1.204574; batch adversarial loss: 1.094635
epoch 1; iter: 0; batch classifier loss: 1.130400; batch adversarial loss: 1.121449
epoch 2; iter: 0; batch classifier loss: 1.177347; batch adversarial loss: 1.064788
epoch 3; iter: 0; batch classifier loss: 1.313622; batch adversarial loss: 1.115868
epoch 4; iter: 0; batch classifier loss: 1.177617; batch adversarial loss: 1.096151
epoch 5; iter: 0; batch classifier loss: 1.034665; batch adversarial loss: 1.089342
epoch 6; iter: 0; batch classifier loss: 0.943962; batch adversarial loss: 1.013924
epoch 7; iter: 0; batch classifier loss: 0.798545; batch adversarial loss: 1.049452
epoch 8; iter: 0; batch classifier loss: 0.757769; batch adversarial loss: 1.020550
epoch 9; iter: 0; batch classifier loss: 0.775128; batch adversarial loss: 1.048202
Out[96]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1a58720ca48>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [97]:
fair_ad
Out[97]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.609005 0.684512 0.491546 -0.416008 -0.477506 -0.401475 0.083794 [0.8360189573459714] 0.150627
Gender 0.609005 0.684512 0.285269 -0.706671 -0.507436 -0.687245 -0.152497 [0.8360189573459714] 0.150627
Marital_Status 0.609005 0.684512 0.474886 -0.525114 -0.377551 -0.511090 -0.053422 [0.8360189573459714] 0.150627
In [98]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model = PrejudiceRemover()

# Train and save the model
debiased_model.fit(data_orig_train)

fair_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
fair_pr
Out[98]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1a5875596c8>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[98]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.744076 0.741627 0.267114 -0.417523 -0.481294 -0.361982 0.048024 [0.7355450236966818] 0.193434
Gender 0.744076 0.741627 0.720477 -0.149219 -0.173973 -0.103168 0.061789 [0.7355450236966818] 0.193434
Marital_Status 0.744076 0.741627 0.744863 -0.140765 -0.155483 -0.073803 0.075489 [0.7355450236966818] 0.193434

POST PROCESSING

In [99]:
y_pred = debiased_model.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [100]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_xgb.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_xgb.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)
In [101]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP = EOPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = EOPP.predict(data_orig_test_pred)
fair_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [102]:
fair_eo
Out[102]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.703791 0.685139 0.496308 -0.242688 -0.274033 -0.191002 0.024308 [0.6791469194312791] 0.247778
Gender 0.703791 0.685139 0.888386 -0.049933 -0.001566 -0.008632 -0.022460 [0.6791469194312791] 0.247778
Marital_Status 0.703791 0.685139 0.820729 -0.084779 -0.013144 -0.030928 -0.036732 [0.6791469194312791] 0.247778
In [103]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=40)

CPP = CPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = CPP.predict(data_orig_test_pred)
fair_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [104]:
fair_ceo
Out[104]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.651659 0.715667 0.518267 -0.383926 -0.374270 -0.345972 0.041041 [0.7672985781990516] 0.133674
Gender 0.651659 0.715667 0.389744 -0.562078 -0.358317 -0.532744 -0.145845 [0.7672985781990516] 0.133674
Marital_Status 0.651659 0.715667 0.581217 -0.381650 -0.241612 -0.352503 -0.040691 [0.7672985781990516] 0.133674
In [105]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC = ROC.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = ROC.predict(data_orig_test_pred)
fair_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
SUCCESS: completed 1 model.
In [106]:
fair_roc
Out[106]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.777251 0.771845 0.500506 -0.260343 -0.230624 -0.178835 0.007049 [0.7061611374407573] 0.176147
Gender 0.777251 0.771845 0.905849 -0.045306 -0.032290 0.009340 0.033063 [0.7061611374407573] 0.176147
Marital_Status 0.777251 0.771845 0.871875 -0.063747 0.016084 0.010716 -0.016915 [0.7061611374407573] 0.176147
In [ ]:
 
In [ ]:
 

3. XGBOOST with out hyper-parameter tuning

In [107]:
from xgboost import XGBClassifier
model_xgb2 = XGBClassifier(seed=40)
In [108]:
mdl_xgb2 = model_xgb2.fit(data_orig_train.features, data_orig_train.labels.ravel())

3.a. Feature importance of model

In [109]:
importances_xg2 = model_xgb2.feature_importances_
indices_xg2 = np.argsort(importances_xg2)
features2 = data_orig_train.feature_names
#https://stackoverflow.com/questions/48377296/get-feature-importance-from-gridsearchcv
In [110]:
importances_xg2
Out[110]:
array([0.02843533, 0.06021052, 0.0338583 , 0.03527502, 0.02776059,
       0.10987206, 0.02087617, 0.04106083, 0.04939539, 0.04555158,
       0.03899714, 0.03430477, 0.04712497, 0.08066472, 0.02615826,
       0.07630543, 0.03524379, 0.        , 0.01186626, 0.11816876,
       0.03129282, 0.02249074, 0.02508657], dtype=float32)
In [111]:
importances_xg2[indices_xg2]
Out[111]:
array([0.        , 0.01186626, 0.02087617, 0.02249074, 0.02508657,
       0.02615826, 0.02776059, 0.02843533, 0.03129282, 0.0338583 ,
       0.03430477, 0.03524379, 0.03527502, 0.03899714, 0.04106083,
       0.04555158, 0.04712497, 0.04939539, 0.06021052, 0.07630543,
       0.08066472, 0.10987206, 0.11816876], dtype=float32)
In [112]:
features2
Out[112]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [113]:
plt.figure(figsize=(20,30))
plt.title('Feature Importances')
plt.barh(range(len(indices_xg2)), importances_xg2[indices_xg2], color='b', align='center')
plt.yticks(range(len(indices_xg2)), [features2[i] for i in indices_xg2])
plt.xlabel('Relative Importance')
plt.show()
Out[113]:
<Figure size 1440x2160 with 0 Axes>
Out[113]:
Text(0.5, 1.0, 'Feature Importances')
Out[113]:
<BarContainer object of 23 artists>
Out[113]:
([<matplotlib.axis.YTick at 0x1a58b02ef08>,
  <matplotlib.axis.YTick at 0x1a58b05e408>,
  <matplotlib.axis.YTick at 0x1a58b053c08>,
  <matplotlib.axis.YTick at 0x1a58b0be208>,
  <matplotlib.axis.YTick at 0x1a58b0bf748>,
  <matplotlib.axis.YTick at 0x1a58b0bf908>,
  <matplotlib.axis.YTick at 0x1a58b0c4448>,
  <matplotlib.axis.YTick at 0x1a58b0c4dc8>,
  <matplotlib.axis.YTick at 0x1a58b0c8548>,
  <matplotlib.axis.YTick at 0x1a58b0c8d48>,
  <matplotlib.axis.YTick at 0x1a58b0cc608>,
  <matplotlib.axis.YTick at 0x1a58b0d1108>,
  <matplotlib.axis.YTick at 0x1a58b0d1bc8>,
  <matplotlib.axis.YTick at 0x1a58b0d56c8>,
  <matplotlib.axis.YTick at 0x1a58b0d1148>,
  <matplotlib.axis.YTick at 0x1a58b0c8a88>,
  <matplotlib.axis.YTick at 0x1a58b0d8948>,
  <matplotlib.axis.YTick at 0x1a58b0da0c8>,
  <matplotlib.axis.YTick at 0x1a58b0dab08>,
  <matplotlib.axis.YTick at 0x1a58b0df548>,
  <matplotlib.axis.YTick at 0x1a58b0e2188>,
  <matplotlib.axis.YTick at 0x1a58b0e2b08>,
  <matplotlib.axis.YTick at 0x1a58b0e6608>],
 <a list of 23 Text yticklabel objects>)
Out[113]:
Text(0.5, 0, 'Relative Importance')

3.b. Model Explainability/interpretability

3.b.1 Using SHAP (SHapley Additive exPlanations)

In [114]:
import shap
xg_shap_values_t = shap.KernelExplainer(mdl_xgb2.predict,data_orig_train.features)
WARNING:shap:Using 983 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

Test data interpretation

In [115]:
xgb_explainer2 = shap.KernelExplainer(mdl_xgb2.predict, data_orig_test.features)
xgb_shap_values2 = xgb_explainer2.shap_values(data_orig_test.features,nsamples=10)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
WARNING:shap:Using 422 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [116]:
xgb_shap_values2
Out[116]:
array([[ 0.        , -0.31753555,  0.        , ...,  0.        ,
         0.36729858,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       ...,
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ]])
In [118]:
shap.initjs()
shap.force_plot(xgb_explainer2.expected_value,xgb_shap_values2[0:,],  data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
Out[118]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

The field age is pushing target outcome towards lower value and collateral unknown/none, purpose others are pushing the target towards higher value which resulted in the final probability of occurrance as .73

In [218]:
shap.initjs()
shap.force_plot(xgb_explainer2.expected_value,xgb_shap_values2[1,:],  data_orig_test.features[1],data_orig_test.feature_names,link='logit')
Out[218]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

Here only credit amount has impact in moving target outcome towards lower value than the base value which is the mean value to target outcome

In [219]:
data_orig_test.feature_names
Out[219]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [220]:
shap.force_plot(xgb_explainer2.expected_value,
                xgb_shap_values2, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[220]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [221]:
p = shap.summary_plot(xgb_shap_values2, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

variables with higher impact are CreditAmount,NumMonths,Savings.

In [222]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer2.expected_value, xgb_shap_values2[0,:],feature_names=data_orig_test.feature_names)

Here purpose other and collateral unknown/none are pushing target to higher value and age is pushing it towards lower value.

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

In [223]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer2.expected_value, xgb_shap_values2[1],feature_names=data_orig_test.feature_names)

3.b.2 Using ELI5

In [224]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [225]:
perm_xgb2 = PermutationImportance(mdl_xgb2).fit(data_orig_test.features, data_orig_test.labels.ravel())

Feature Importance

In [226]:
perm_imp_3=eli5.show_weights(perm_xgb2,feature_names = data_orig_test.feature_names)
perm_imp_3
plt.show()
Out[226]:
Weight Feature
0.0820 ± 0.0554 CreditAmount
0.0796 ± 0.0111 CreditHistory_other
0.0687 ± 0.0265 NumMonths
0.0355 ± 0.0144 Savings_none
0.0265 ± 0.0274 Purpose_CarUsed
0.0232 ± 0.0154 Age
0.0190 ± 0.0218 CreditHistory_none/paid
0.0175 ± 0.0202 Collateral_real estate
0.0175 ± 0.0133 PayBackPercent
0.0104 ± 0.0152 Debtors_none
0.0090 ± 0.0076 Debtors_guarantor
0.0085 ± 0.0023 Purpose_furniture/equip
0.0066 ± 0.0139 Purpose_CarNew
0.0062 ± 0.0129 Savings_<500
0.0057 ± 0.0038 Dependents
0.0043 ± 0.0070 Property_rent
0.0043 ± 0.0046 Collateral_unknown/none
0.0019 ± 0.0121 Marital_Status
0 ± 0.0000 Job_unemp/unskilled-non resident
0 ± 0.0000 Purpose_others
… 3 more …

Explaining individual predictions

In [227]:
from eli5 import show_prediction
show_prediction(mdl_xgb2, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[227]:

y=0.0 (probability 0.922, score -2.465) top features

Contribution? Feature Value
+1.303 CreditAmount 0.321
+1.251 NumMonths 12.000
+0.297 Savings_none 0.000
+0.286 CreditHistory_other 0.000
+0.170 Collateral_real estate 0.000
+0.149 Purpose_CarUsed 0.000
+0.129 Dependents 1.000
+0.127 Purpose_furniture/equip 0.000
+0.126 Gender 1.000
+0.104 Job_management/self-emp/officer/highly qualif emp 0.000
+0.035 Property_rent 0.000
+0.032 <BIAS> 1.000
+0.024 Debtors_guarantor 0.000
+0.018 CreditHistory_none/paid 1.000
+0.004 Purpose_others 0.000
-0.003 Purpose_education 0.000
-0.036 Collateral_unknown/none 0.000
-0.094 Debtors_none 1.000
-0.127 Purpose_CarNew 1.000
-0.148 Age 1.000
-0.151 Savings_<500 1.000
-0.486 Marital_Status 1.000
-0.545 PayBackPercent 2.000
In [ ]:
 

3.c. Measuring fairness

Of Baseline model

In [228]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_xgb2, X_test, y_test)
In [229]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, mdl_xgb2)
fair
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[229]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.765403 0.765957 0.426557 -0.321476 -0.281294 -0.243143 -0.008103 0.711848 0.172607
Gender 0.765403 0.765957 0.632365 -0.208695 -0.179648 -0.157556 0.014266 0.711848 0.172607
Marital_Status 0.765403 0.765957 0.672032 -0.193873 -0.084054 -0.126470 -0.041546 0.711848 0.172607

PRE PROCESSING

In [230]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train = RW.fit_transform(data_orig_train)

data_transf_test = RW.transform(data_orig_test)
fair_rw = get_fair_metrics_and_plot(filename, data_transf_test, mdl_xgb2, plot=False)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [231]:
fair_rw
Out[231]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.7549 0.757053 0.455128 -0.304307 -0.291026 -0.250311 0.012032 0.711848 0.172607
Gender 0.7549 0.757053 0.744248 -0.138956 -0.179648 -0.157556 0.031992 0.711848 0.172607
Marital_Status 0.7549 0.757053 0.737531 -0.149578 -0.098921 -0.133005 -0.030135 0.711848 0.172607
In [232]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train = DIR.fit_transform(data_orig_train)
In [233]:
fair_dir = get_fair_metrics_and_plot(filename, data_orig_test, mdl_xgb2, plot=False)
fair_dir
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[233]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.765403 0.765957 0.426557 -0.321476 -0.281294 -0.243143 -0.008103 0.711848 0.172607
Gender 0.765403 0.765957 0.632365 -0.208695 -0.179648 -0.157556 0.014266 0.711848 0.172607
Marital_Status 0.765403 0.765957 0.672032 -0.193873 -0.084054 -0.126470 -0.041546 0.711848 0.172607

INPROCESSING

In [234]:
#!pip install tensorflow
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [235]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [236]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [237]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope3',reuse=tf.AUTO_REUSE) as scope:
        debiased_model = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model.fit(data_orig_train)
        fair_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 0.866650; batch adversarial loss: 0.778298
epoch 1; iter: 0; batch classifier loss: 0.725636; batch adversarial loss: 0.761601
epoch 2; iter: 0; batch classifier loss: 0.874915; batch adversarial loss: 0.817991
epoch 3; iter: 0; batch classifier loss: 0.882604; batch adversarial loss: 0.834015
epoch 4; iter: 0; batch classifier loss: 0.872095; batch adversarial loss: 0.868937
epoch 5; iter: 0; batch classifier loss: 0.766373; batch adversarial loss: 0.789082
epoch 6; iter: 0; batch classifier loss: 0.805934; batch adversarial loss: 0.838473
epoch 7; iter: 0; batch classifier loss: 0.644151; batch adversarial loss: 0.781086
epoch 8; iter: 0; batch classifier loss: 0.759194; batch adversarial loss: 0.841978
epoch 9; iter: 0; batch classifier loss: 0.727526; batch adversarial loss: 0.831974
Out[237]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1a597f7b548>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [238]:
fair_ad
Out[238]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.620853 0.683794 0.353261 -0.517391 -0.546646 -0.496153 0.029447 [0.8270142180094784] 0.165438
Gender 0.620853 0.683794 0.226462 -0.744457 -0.593151 -0.724192 -0.113360 [0.8270142180094784] 0.165438
Marital_Status 0.620853 0.683794 0.410436 -0.583755 -0.438776 -0.567127 -0.057269 [0.8270142180094784] 0.165438
In [239]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model = PrejudiceRemover()

# Train and save the model
debiased_model.fit(data_orig_train)

fair_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
fair_pr
Out[239]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1a597f8bfc8>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[239]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.744076 0.741627 0.267114 -0.417523 -0.481294 -0.361982 0.048024 [0.7355450236966818] 0.193434
Gender 0.744076 0.741627 0.720477 -0.149219 -0.173973 -0.103168 0.061789 [0.7355450236966818] 0.193434
Marital_Status 0.744076 0.741627 0.744863 -0.140765 -0.155483 -0.073803 0.075489 [0.7355450236966818] 0.193434

POST PROCESSING

In [240]:
y_pred = debiased_model.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [241]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_xgb.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_xgb.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)
In [242]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP = EOPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = EOPP.predict(data_orig_test_pred)
fair_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [243]:
fair_eo
Out[243]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.703791 0.685139 0.496308 -0.242688 -0.274033 -0.191002 0.024308 [0.6791469194312791] 0.247778
Gender 0.703791 0.685139 0.888386 -0.049933 -0.001566 -0.008632 -0.022460 [0.6791469194312791] 0.247778
Marital_Status 0.703791 0.685139 0.820729 -0.084779 -0.013144 -0.030928 -0.036732 [0.6791469194312791] 0.247778
In [244]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=40)

CPP = CPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = CPP.predict(data_orig_test_pred)
fair_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [245]:
fair_ceo
Out[245]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.651659 0.715667 0.518267 -0.383926 -0.374270 -0.345972 0.041041 [0.7672985781990516] 0.133674
Gender 0.651659 0.715667 0.389744 -0.562078 -0.358317 -0.532744 -0.145845 [0.7672985781990516] 0.133674
Marital_Status 0.651659 0.715667 0.581217 -0.381650 -0.241612 -0.352503 -0.040691 [0.7672985781990516] 0.133674
In [246]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC = ROC.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = ROC.predict(data_orig_test_pred) 
fair_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
SUCCESS: completed 1 model.
In [247]:
fair_roc
Out[247]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.777251 0.771845 0.500506 -0.260343 -0.230624 -0.178835 0.007049 [0.7061611374407573] 0.176147
Gender 0.777251 0.771845 0.905849 -0.045306 -0.032290 0.009340 0.033063 [0.7061611374407573] 0.176147
Marital_Status 0.777251 0.771845 0.871875 -0.063747 0.016084 0.010716 -0.016915 [0.7061611374407573] 0.176147
In [ ]:
 

4. RANDOM FOREST CLASSIFIER MODEL WITH OUT HYPER-PARAMETER TUNING

In [248]:
#Creating the classifier
rf_model2 = RandomForestClassifier(random_state=40)
model=rf_model2
In [249]:
mdl_rf2 = model.fit(data_orig_train.features, data_orig_train.labels.ravel())
In [250]:
from sklearn.metrics import confusion_matrix
conf_mat_rf = confusion_matrix(data_orig_test.labels.ravel(), mdl_rf2.predict(data_orig_test.features))
conf_mat_rf
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), mdl_rf2.predict(data_orig_test.features)))
Out[250]:
array([[172,  34],
       [ 56, 160]], dtype=int64)
0.7867298578199052
In [251]:
unique, counts = np.unique(data_orig_test.labels.ravel(), return_counts=True)
dict(zip(unique, counts))
Out[251]:
{0.0: 206, 1.0: 216}

4.a. Model Explainability/interpretability

4.a.1 Using SHAP (SHapley Additive exPlanations)

In [252]:
import shap
rf_shap_values_t2 = shap.KernelExplainer(mdl_rf2.predict,data_orig_train.features)
WARNING:shap:Using 983 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

Test data interpretation

In [322]:
rf_explainer2 = shap.KernelExplainer(mdl_rf2.predict, data_orig_test.features)
rf_shap_values2 = rf_explainer2.shap_values(data_orig_test.features,nsamples=10)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
WARNING:shap:Using 422 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [323]:
rf_shap_values2
Out[323]:
array([[0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       ...,
       [0.        , 0.        , 0.54028436, ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ]])
In [342]:
rf_explainer2.expected_value
rf_shap_values2
Out[342]:
0.4597156398104266
Out[342]:
array([[0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       ...,
       [0.        , 0.        , 0.54028436, ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ]])
In [344]:
shap.initjs()
shap.force_plot(rf_explainer2.expected_value,rf_shap_values2[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
Out[344]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [333]:
shap.initjs()
shap.force_plot(rf_explainer2.expected_value,rf_shap_values2[1,:], data_orig_test.features[1],data_orig_test.feature_names,link='logit')
Out[333]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [334]:
shap.initjs()
shap.force_plot(rf_explainer2.expected_value,rf_shap_values2[2,:], data_orig_test.features[2],data_orig_test.feature_names,link='logit')
Out[334]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [326]:
data_orig_test.feature_names
Out[326]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [327]:
shap.force_plot(rf_explainer2.expected_value,
                rf_shap_values2, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[327]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [328]:
p = shap.summary_plot(rf_shap_values2, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

Variables with higher impact are displayed at the top such as gender,age,nummonths etc

In [329]:
shap.plots._waterfall.waterfall_legacy(rf_explainer2.expected_value, rf_shap_values2[0,:],feature_names=data_orig_test.feature_names)

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

In [330]:
shap.plots._waterfall.waterfall_legacy(rf_explainer2.expected_value, rf_shap_values2[1],feature_names=data_orig_test.feature_names)

4.a.2 Using ELI5

In [263]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [264]:
perm_rf2 = PermutationImportance(mdl_rf2).fit(data_orig_test.features, data_orig_test.labels.ravel())
In [265]:
data_orig_test.labels[:10,:].ravel()
Out[265]:
array([1., 1., 1., 0., 0., 1., 0., 1., 1., 1.])

Feature Importance

In [266]:
perm_imp_11=eli5.show_weights(perm_rf2,feature_names = data_orig_test.feature_names)
perm_imp_11
plt.show()
Out[266]:
Weight Feature
0.0957 ± 0.0262 CreditHistory_other
0.0682 ± 0.0154 NumMonths
0.0360 ± 0.0311 PayBackPercent
0.0275 ± 0.0211 CreditAmount
0.0265 ± 0.0151 Collateral_real estate
0.0237 ± 0.0067 CreditHistory_none/paid
0.0237 ± 0.0263 Savings_none
0.0218 ± 0.0213 Purpose_CarUsed
0.0123 ± 0.0046 Debtors_guarantor
0.0095 ± 0.0095 Marital_Status
0.0085 ± 0.0111 Job_management/self-emp/officer/highly qualif emp
0.0081 ± 0.0142 Savings_<500
0.0071 ± 0.0030 Debtors_none
0.0066 ± 0.0148 Purpose_furniture/equip
0.0062 ± 0.0115 Gender
0.0062 ± 0.0048 Property_rent
0.0047 ± 0.0090 Dependents
0.0043 ± 0.0176 Age
0.0000 ± 0.0030 Purpose_education
0.0000 ± 0.0030 Job_unemp/unskilled-non resident
… 3 more …

Explaining individual predictions

In [267]:
show_prediction(mdl_rf2, data_orig_test.features[0], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[267]:

y=1.0 (probability 0.560) top features

Contribution? Feature Value
+0.492 <BIAS> 1.000
+0.190 CreditHistory_other 1.000
+0.031 Purpose_furniture/equip 1.000
+0.024 CreditHistory_none/paid 0.000
+0.023 CreditAmount 0.044
+0.015 Debtors_none 1.000
+0.009 Gender 0.000
+0.006 Purpose_education 0.000
-0.000 Dependents 1.000
-0.000 Purpose_others 0.000
-0.000 Purpose_CarNew 0.000
-0.001 Collateral_unknown/none 0.000
-0.003 Debtors_guarantor 0.000
-0.003 Job_unemp/unskilled-non resident 0.000
-0.004 PayBackPercent 4.000
-0.007 Job_management/self-emp/officer/highly qualif emp 0.000
-0.009 Purpose_CarUsed 0.000
-0.010 Savings_<500 1.000
-0.013 Collateral_real estate 0.000
-0.014 Savings_none 0.000
-0.033 NumMonths 18.000
-0.036 Property_rent 1.000
-0.037 Marital_Status 0.000
-0.059 Age 0.000
In [268]:
from eli5 import show_prediction
show_prediction(mdl_rf2, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[268]:

y=0.0 (probability 0.640) top features

Contribution? Feature Value
+0.508 <BIAS> 1.000
+0.158 CreditAmount 0.321
+0.053 CreditHistory_other 0.000
+0.035 Collateral_real estate 0.000
+0.026 Savings_none 0.000
+0.020 Savings_<500 1.000
+0.019 NumMonths 12.000
+0.019 Purpose_CarUsed 0.000
+0.007 Purpose_CarNew 1.000
+0.006 Debtors_guarantor 0.000
+0.005 Dependents 1.000
+0.004 Job_management/self-emp/officer/highly qualif emp 0.000
+0.002 Property_rent 0.000
+0.001 Purpose_furniture/equip 0.000
+0.000 Job_unemp/unskilled-non resident 0.000
-0.001 Purpose_education 0.000
-0.003 Purpose_others 0.000
-0.007 Collateral_unknown/none 0.000
-0.010 CreditHistory_none/paid 1.000
-0.012 Debtors_none 1.000
-0.016 Gender 1.000
-0.030 Age 1.000
-0.066 Marital_Status 1.000
-0.078 PayBackPercent 2.000

4.b. Measuring fairness

Of Baseline model

In [269]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_rf2, X_test, y_test)
In [270]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, mdl_rf2)
fair
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[270]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.78673 0.780488 0.482456 -0.268182 -0.304341 -0.193499 0.060870 0.724171 0.171376
Gender 0.78673 0.780488 0.709433 -0.149653 -0.144814 -0.094306 0.037931 0.724171 0.171376
Marital_Status 0.78673 0.780488 0.707846 -0.158310 -0.085783 -0.082658 -0.006703 0.724171 0.171376
In [271]:
type(data_orig_train)
Out[271]:
aif360.datasets.binary_label_dataset.BinaryLabelDataset

PRE PROCESSING

In [272]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train = RW.fit_transform(data_orig_train)

data_transf_test = RW.transform(data_orig_test)
fair_rw = get_fair_metrics_and_plot(filename, data_transf_test, mdl_rf2, plot=False)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [273]:
fair_rw
Out[273]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.779817 0.774588 0.521710 -0.245585 -0.310629 -0.192350 0.086650 0.724171 0.171376
Gender 0.779817 0.774588 0.845437 -0.075341 -0.144814 -0.094306 0.059454 0.724171 0.171376
Marital_Status 0.779817 0.774588 0.789874 -0.108474 -0.095829 -0.088392 0.007273 0.724171 0.171376
In [274]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train = DIR.fit_transform(data_orig_train)

# Train and save the model
#rf_transf = model.fit(data_transf_train.features,data_transf_train.labels.ravel())
In [275]:
fair_dir = get_fair_metrics_and_plot(filename, data_orig_test, mdl_rf2, plot=False)
fair_dir
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[275]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.78673 0.780488 0.482456 -0.268182 -0.304341 -0.193499 0.060870 0.724171 0.171376
Gender 0.78673 0.780488 0.709433 -0.149653 -0.144814 -0.094306 0.037931 0.724171 0.171376
Marital_Status 0.78673 0.780488 0.707846 -0.158310 -0.085783 -0.082658 -0.006703 0.724171 0.171376

INPROCESSING

In [276]:
#!pip install --user --upgrade tensorflow==1.15.0
#2.2.0
#!pip uninstall tensorflow
In [277]:
#!pip install "tensorflow==1.15"
#!pip install --upgrade tensorflow-hub
In [278]:
#%tensorflow_version 1.15
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [279]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [280]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [281]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope1',reuse=tf.AUTO_REUSE) as scope:
        debiased_model = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model.fit(data_orig_train)
        fair_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 0.905645; batch adversarial loss: 0.776539
epoch 1; iter: 0; batch classifier loss: 0.794631; batch adversarial loss: 0.807912
epoch 2; iter: 0; batch classifier loss: 0.777869; batch adversarial loss: 0.937515
epoch 3; iter: 0; batch classifier loss: 0.890375; batch adversarial loss: 1.130312
epoch 4; iter: 0; batch classifier loss: 1.011895; batch adversarial loss: 1.168322
epoch 5; iter: 0; batch classifier loss: 1.155322; batch adversarial loss: 1.081472
epoch 6; iter: 0; batch classifier loss: 1.107303; batch adversarial loss: 1.064201
epoch 7; iter: 0; batch classifier loss: 0.944990; batch adversarial loss: 1.076516
epoch 8; iter: 0; batch classifier loss: 0.692587; batch adversarial loss: 1.041655
epoch 9; iter: 0; batch classifier loss: 0.659902; batch adversarial loss: 1.083533
Out[281]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1a59ba111c8>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [282]:
fair_ad
Out[282]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.654028 0.706827 0.508273 -0.368050 -0.358642 -0.328856 0.030171 [0.8293838862559241] 0.155811
Gender 0.654028 0.706827 0.315234 -0.612686 -0.465753 -0.584718 -0.101407 [0.8293838862559241] 0.155811
Marital_Status 0.654028 0.706827 0.501460 -0.449423 -0.333449 -0.419714 -0.026273 [0.8293838862559241] 0.155811
In [283]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model = PrejudiceRemover()

# Train and save the model
debiased_model.fit(data_orig_train)

fair_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
fair_pr
Out[283]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1a59c15e548>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[283]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.744076 0.741627 0.267114 -0.417523 -0.481294 -0.361982 0.048024 [0.7355450236966818] 0.193434
Gender 0.744076 0.741627 0.720477 -0.149219 -0.173973 -0.103168 0.061789 [0.7355450236966818] 0.193434
Marital_Status 0.744076 0.741627 0.744863 -0.140765 -0.155483 -0.073803 0.075489 [0.7355450236966818] 0.193434

POST PROCESSING

In [284]:
y_pred = debiased_model.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [285]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_rf2.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_rf2.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)
In [286]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP = EOPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = EOPP.predict(data_orig_test_pred)
fair_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [287]:
fair_eo
Out[287]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.746445 0.723514 0.475094 -0.240184 -0.296133 -0.179327 0.051054 [0.7109004739336484] 0.226753
Gender 0.746445 0.723514 0.875199 -0.053017 -0.007828 -0.003332 -0.015809 [0.7109004739336484] 0.226753
Marital_Status 0.746445 0.723514 0.795960 -0.092471 0.008993 -0.027637 -0.061970 [0.7109004739336484] 0.226753
In [288]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=42)

CPP = CPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = CPP.predict(data_orig_test_pred)
fair_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [289]:
fair_ceo
Out[289]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.64218 0.716698 0.593430 -0.335112 -0.339227 -0.301369 0.070619 [0.8052132701421796] 0.119407
Gender 0.64218 0.716698 0.381843 -0.601889 -0.336008 -0.573236 -0.201562 [0.8052132701421796] 0.119407
Marital_Status 0.64218 0.716698 0.564634 -0.422498 -0.236423 -0.398717 -0.079380 [0.8052132701421796] 0.119407
In [290]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC = ROC.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = ROC.predict(data_orig_test_pred)
fair_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
SUCCESS: completed 1 model.
In [291]:
fair_roc
Out[291]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.803318 0.811791 0.660755 -0.195323 -0.102447 -0.096437 -0.001318 [0.7056872037914681] 0.126854
Gender 0.803318 0.811791 0.922638 -0.042462 -0.000196 0.017829 0.023569 [0.7056872037914681] 0.126854
Marital_Status 0.803318 0.811791 0.840531 -0.092696 0.014701 -0.012873 -0.019682 [0.7056872037914681] 0.126854

5. KNN

In [292]:
from sklearn import neighbors
n_neighbors = 15
knn = neighbors.KNeighborsClassifier(n_neighbors, weights='distance')
In [293]:
knn.fit(data_orig_train.features, data_orig_train.labels.ravel())
Out[293]:
KNeighborsClassifier(n_neighbors=15, weights='distance')

5.a. Model Explainability/interpretability

5.a.1 Using SHAP (SHapley Additive exPlanations)

In [294]:
knn_explainer = shap.KernelExplainer(knn.predict, data_orig_test.features)
knn_shap_values = knn_explainer.shap_values(data_orig_test.features,nsamples=10)
WARNING:shap:Using 422 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [295]:
#shap.dependence_plot(0, knn_shap_values, data_orig_test.features)
In [296]:
# plot the SHAP values for the 0th observation 
shap.force_plot(knn_explainer.expected_value,knn_shap_values[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit') 
Out[296]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [297]:
# plot the SHAP values for the 1st observation 
shap.force_plot(knn_explainer.expected_value,knn_shap_values[1,:],  data_orig_test.features[1],data_orig_test.feature_names,link='logit') 
Out[297]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [298]:
shap.force_plot(knn_explainer.expected_value, knn_shap_values,  data_orig_test.feature_names,link='logit')
Out[298]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [299]:
shap.summary_plot(knn_shap_values, data_orig_test.features,feature_names=data_orig_test.feature_names, plot_type="violin")

Feature Importance

perm_imp_11=eli5.show_weights(knn,feature_names = data_orig_test.feature_names) perm_imp_11 plt.show()

Explaining individual predictions

In [300]:
from eli5 import show_prediction
show_prediction(mdl_rf2, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[300]:

y=0.0 (probability 0.640) top features

Contribution? Feature Value
+0.508 <BIAS> 1.000
+0.158 CreditAmount 0.321
+0.053 CreditHistory_other 0.000
+0.035 Collateral_real estate 0.000
+0.026 Savings_none 0.000
+0.020 Savings_<500 1.000
+0.019 NumMonths 12.000
+0.019 Purpose_CarUsed 0.000
+0.007 Purpose_CarNew 1.000
+0.006 Debtors_guarantor 0.000
+0.005 Dependents 1.000
+0.004 Job_management/self-emp/officer/highly qualif emp 0.000
+0.002 Property_rent 0.000
+0.001 Purpose_furniture/equip 0.000
+0.000 Job_unemp/unskilled-non resident 0.000
-0.001 Purpose_education 0.000
-0.003 Purpose_others 0.000
-0.007 Collateral_unknown/none 0.000
-0.010 CreditHistory_none/paid 1.000
-0.012 Debtors_none 1.000
-0.016 Gender 1.000
-0.030 Age 1.000
-0.066 Marital_Status 1.000
-0.078 PayBackPercent 2.000

5.b. Measuring fairness

Of Baseline model

In [301]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(knn, X_test, y_test)
In [302]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, knn)
fair
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[302]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.71564 0.662921 0.462833 -0.201845 -0.242778 -0.146704 0.011660 0.760664 0.286712
Gender 0.71564 0.662921 0.635244 -0.139869 -0.131898 -0.097732 -0.003663 0.760664 0.286712
Marital_Status 0.71564 0.662921 0.780582 -0.082147 -0.028710 -0.023592 -0.040579 0.760664 0.286712

PRE PROCESSING

In [303]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train = RW.fit_transform(data_orig_train)

data_transf_test = RW.transform(data_orig_test)
fair_rw = get_fair_metrics_and_plot(filename, data_transf_test, knn, plot=False)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [304]:
fair_rw
Out[304]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.707147 0.655102 0.505787 -0.183921 -0.243166 -0.144458 0.042302 0.760664 0.286712
Gender 0.707147 0.655102 0.770840 -0.083105 -0.131898 -0.097732 0.052926 0.760664 0.286712
Marital_Status 0.707147 0.655102 0.877010 -0.043593 -0.043175 -0.027960 0.003843 0.760664 0.286712
In [305]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train = DIR.fit_transform(data_orig_train)
In [306]:
fair_dir = get_fair_metrics_and_plot(filename, data_orig_test, knn, plot=False)
fair_dir
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[306]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Age 0.71564 0.662921 0.462833 -0.201845 -0.242778 -0.146704 0.011660 0.760664 0.286712
Gender 0.71564 0.662921 0.635244 -0.139869 -0.131898 -0.097732 -0.003663 0.760664 0.286712
Marital_Status 0.71564 0.662921 0.780582 -0.082147 -0.028710 -0.023592 -0.040579 0.760664 0.286712

INPROCESSING

In [307]:
#!pip install tensorflow
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [308]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [309]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [310]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope4',reuse=tf.AUTO_REUSE) as scope:
        debiased_model = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model.fit(data_orig_train)
        fair_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 0.929728; batch adversarial loss: 0.874201
epoch 1; iter: 0; batch classifier loss: 1.034081; batch adversarial loss: 0.971586
epoch 2; iter: 0; batch classifier loss: 0.927143; batch adversarial loss: 1.139856
epoch 3; iter: 0; batch classifier loss: 0.996121; batch adversarial loss: 1.068526
epoch 4; iter: 0; batch classifier loss: 0.972750; batch adversarial loss: 1.089134
epoch 5; iter: 0; batch classifier loss: 0.981893; batch adversarial loss: 1.058156
epoch 6; iter: 0; batch classifier loss: 0.930992; batch adversarial loss: 1.028271
epoch 7; iter: 0; batch classifier loss: 0.776863; batch adversarial loss: 1.006241
epoch 8; iter: 0; batch classifier loss: 0.685059; batch adversarial loss: 1.095320
epoch 9; iter: 0; batch classifier loss: 0.610194; batch adversarial loss: 1.031411
Out[310]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1a598211948>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [311]:
fair_ad
Out[311]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.646919 0.711799 0.471968 -0.425626 -0.402841 -0.387868 0.021080 [0.8312796208530805] 0.136408
Gender 0.646919 0.711799 0.339667 -0.623096 -0.436008 -0.595717 -0.133025 [0.8312796208530805] 0.136408
Marital_Status 0.646919 0.711799 0.503875 -0.476573 -0.307852 -0.451592 -0.069528 [0.8312796208530805] 0.136408
In [312]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model = PrejudiceRemover()

# Train and save the model
debiased_model.fit(data_orig_train)

fair_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model, plot=False, model_aif=True)
fair_pr
Out[312]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1a59b555688>
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
Out[312]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.744076 0.741627 0.267114 -0.417523 -0.481294 -0.361982 0.048024 [0.7355450236966818] 0.193434
Gender 0.744076 0.741627 0.720477 -0.149219 -0.173973 -0.103168 0.061789 [0.7355450236966818] 0.193434
Marital_Status 0.744076 0.741627 0.744863 -0.140765 -0.155483 -0.073803 0.075489 [0.7355450236966818] 0.193434

POST PROCESSING

In [313]:
y_pred = debiased_model.predict(data_orig_test)

data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [314]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = knn.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = knn.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)
In [315]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP = EOPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = EOPP.predict(data_orig_test_pred)
fair_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [316]:
fair_eo
Out[316]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.677725 0.602339 0.559434 -0.145520 -0.194002 -0.101416 0.004875 [0.7431279620853077] 0.335909
Gender 0.677725 0.602339 0.852564 -0.046559 -0.008023 -0.010019 -0.043474 [0.7431279620853077] 0.335909
Marital_Status 0.677725 0.602339 0.956842 -0.013181 0.061052 0.035436 -0.081427 [0.7431279620853077] 0.335909
In [317]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=40)

CPP = CPP.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = CPP.predict(data_orig_test_pred)
fair_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
In [318]:
fair_ceo
Out[318]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.611374 0.684615 0.559183 -0.351318 -0.369692 -0.328729 0.059025 [0.8232227488151657] 0.153091
Gender 0.611374 0.684615 0.243590 -0.756410 -0.542857 -0.736545 -0.169077 [0.8232227488151657] 0.153091
Marital_Status 0.611374 0.684615 0.461187 -0.538813 -0.387755 -0.524456 -0.057989 [0.8232227488151657] 0.153091
In [319]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC = ROC.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred = ROC.predict(data_orig_test_pred) 
fair_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
Computing fairness of the model.
Computing fairness of the model.
SUCCESS: completed 1 model.
In [320]:
fair_roc
Out[320]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Age 0.746445 0.752887 0.713428 -0.157181 -0.116338 -0.081718 0.023254 [0.7308056872037908] 0.174607
Gender 0.746445 0.752887 1.015554 0.007953 0.003718 0.056316 0.055379 [0.7308056872037908] 0.174607
Marital_Status 0.746445 0.752887 1.025934 0.013159 0.056901 0.081343 0.023461 [0.7308056872037908] 0.174607
In [ ]: